package com._21cn.framework.http;

import java.io.Closeable;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.cookie.CookieSpec;
import org.apache.http.cookie.CookieSpecProvider;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.DefaultCookieSpec;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * HttpClient工具类
 * @author 司徒海富
 * 创建时间：2016年4月12日
 */
public class HttpCaller {
    public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.99 Safari/537.36";
    public static final String GET = "GET", POST = "POST";

    private static final String[] DEFAULT_DATE_PATTERNS = new String[]{
        DateUtils.PATTERN_RFC1123,
        DateUtils.PATTERN_RFC1036,
        DateUtils.PATTERN_ASCTIME,
        "EEE, dd-MMM-yyyy HH:mm:ss z",
        "EEE, dd-MMM-yyyy HH-mm-ss z",
        "EEE, dd MMM yy HH:mm:ss z",
        "EEE dd-MMM-yyyy HH:mm:ss z",
        "EEE dd MMM yyyy HH:mm:ss z",
        "EEE dd-MMM-yyyy HH-mm-ss z",
        "EEE dd-MMM-yy HH:mm:ss z",
        "EEE dd MMM yy HH:mm:ss z",
        "EEE,dd-MMM-yy HH:mm:ss z",
        "EEE,dd-MMM-yyyy HH:mm:ss z",
        "EEE, dd-MM-yyyy HH:mm:ss z",};

    private static final Logger log = LoggerFactory.getLogger(HttpCaller.class);
    protected static final CloseableHttpClient CLIENT;
    private static final BasicCookieStore COOKIE_STORE = new BasicCookieStore();

    private RequestBuilder builder;
    private String encoding = "UTF-8";
    private String url = "";

    private CloseableHttpResponse resp;
    private HttpEntity entity;

    private int retry = 0;
    private long startMillsecond;

    static {
        HttpClientBuilder clientBuilder = HttpClients.custom().setUserAgent(HttpCaller.USER_AGENT);

        CookieSpecProvider noInvalidWarningCookieProvider = new CookieSpecProvider() {
            @Override
            public CookieSpec create(HttpContext context) {
                return new DefaultCookieSpec(DEFAULT_DATE_PATTERNS, false);
            }
        };

        RegistryBuilder<CookieSpecProvider> registryBuilder = RegistryBuilder.create();
        Registry<CookieSpecProvider> registry = registryBuilder.register(CookieSpecs.DEFAULT, noInvalidWarningCookieProvider).build();
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(5000)
                .setConnectTimeout(5000)
                .setConnectionRequestTimeout(5000)
                .build();
		clientBuilder.setMaxConnTotal(800)
        	.setMaxConnPerRoute(200)
        	.setDefaultCookieSpecRegistry(registry)
        	.setDefaultRequestConfig(requestConfig)
        	.evictIdleConnections(1, TimeUnit.MINUTES)
        	.setDefaultCookieStore(COOKIE_STORE);

        CLIENT = clientBuilder.build();
    }

    private HttpCaller(String url, String method) {
        this.url = url;
        if (GET.equalsIgnoreCase(method)) {
            this.builder = RequestBuilder.get(url);
        } else if (POST.equalsIgnoreCase(method)) {
            this.builder = RequestBuilder.post(url);
        } else {
            throw new RuntimeException("必须指定请求的方法...");
        }
    }

    public static HttpCaller get(String url) {
        return new HttpCaller(url, GET);
    }

    public static HttpCaller post(String url) {
        return new HttpCaller(url, POST);
    }

    public HttpCaller addHeader(String name, String value) {
        this.builder.addHeader(name, value);
        return this;
    }

    public HttpCaller addParam(String name, String value) {
        this.builder.addParameter(name, value);
        return this;
    }
    
    public HttpCaller setEntity(HttpEntity entity) {
        this.builder.setEntity(entity);
        return this;
    }

    public HttpCaller setTimeout(int millseconds) {
        this.builder.setConfig(RequestConfig.custom().setConnectionRequestTimeout(millseconds).build());
        return this;
    }

    public HttpCaller setEncoding(String encoding) {
        this.encoding = encoding;
        return this;
    }

    public HttpCaller setRetry(int retry) {
        this.retry = retry;
        return this;
    }

    public HttpCaller call() {
        this.startMillsecond = System.currentTimeMillis();
        try {
            this.doCall();
        } catch (Exception e) {
            if (this.retry > 0) {
                log.error("重新对->" + this.url + "发起请求...");
            }
            boolean retrySuccess = false;
            for (int i = 0; !retrySuccess && i < this.retry; i++) {
                try {
                    Thread.sleep(3000);
                    log.error("重新对->" + this.url + "发起第" + (i + 1) + "请求...");
                    this.doCall();
                    log.error("重新对->" + this.url + "发起第" + (i + 1) + "请求成功...");
                    retrySuccess = true;
                } catch (Exception e1) {
                    log.error("第" + (i + 1) + "次重新请求URL-> " + this.url + "失败...", e1);
                }
            }
        }
        return this;
    }

    private void doCall() throws Exception {
        try {
            this.resp = HttpCaller.CLIENT.execute(this.builder.build());
            if (this.resp!=null && this.resp.getStatusLine().getStatusCode() != 200) {
                log.error("请求URL-> " + this.url + "失败，状态码：" + this.resp.getStatusLine().getStatusCode());
            }
            this.entity = resp.getEntity();
        } catch (Exception e) {
            log.error("请求URL-> " + this.url + "失败", e);
            closeQuietly(this.resp);
            this.resp = null;
            this.entity = null;
            throw e;
        }
    }

    public String getString() {
        try {
            if (this.entity!=null) {
                return EntityUtils.toString(this.entity, this.encoding);
            }
        } catch (Exception e) {
            log.error("获取响应内容失败", e);
        } finally {
            EntityUtils.consumeQuietly(this.entity);
            closeQuietly(this.resp);
            this.startMillsecond = 0L;
            this.resp = null;
            this.entity = null;
            this.builder = null;
            this.url = null;
        }
        return "";
    }

    public byte[] getBytes() {
        try {
            if (isNotNull(this.entity)) {
                return EntityUtils.toByteArray(this.entity);
            }
        } catch (Exception e) {
            log.error("获取响应内容失败", e);
        } finally {
            EntityUtils.consumeQuietly(this.entity);
            closeQuietly(this.resp);
            this.startMillsecond = 0L;
            this.resp = null;
            this.entity = null;
            this.builder = null;
            this.url = null;
        }
        return null;
    }
    
    public static List<Cookie> getCookies() {
    	return COOKIE_STORE.getCookies();
    }

    public HttpCaller shutdown() {
        this.builder = null;
        this.resp = null;
        this.entity = null;
        this.url = null;
        closeQuietly(CLIENT);
        return this;
    }

    private static <T extends Closeable> void closeQuietly(T t) {
        try {
            if (isNotNull(t)) {
                t.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static <T extends Object> boolean isNotNull(T t) {
        return t != null;
    }
}
